<template>
    <div v-loading="loading" class="poster-canvas-wrap zw-common-scrollbar">
        <div :style="posterBoxStyle" class="poster-canvas-rect">
            <!-- 头像 -->
            <div
                v-if="isOpen(props.posterHeadSwitch)"
                ref="avatar"
                class="poster-avatar"
                :class="{ 'is-circle': props.posterHead === 1 }"
                :style="{
                    top: formData.position.avatarY + 'px',
                    left: formData.position.avatarX + 'px'
                }"
                @mousedown.stop.capture="e => handleMoveElement(e, 'avatar')">
                <img src="@/assets/channelConfig/squaredefault.png" draggable="false" />
            </div>
            <!-- 昵称 -->
            <div
                v-if="isOpen(props.posterNicknameSwitch)"
                ref="nickname"
                :style="{
                    color: props.posterNicknameColor ?? 'transparent',
                    fontSize: props.posterNicknameSize + 'px',
                    top: formData.position.nicknameY + 'px',
                    left: formData.position.nicknameX + 'px'
                }"
                class="poster-title"
                @mousedown.stop.capture="e => handleMoveElement(e, 'nickname')">
                这里是分享人昵称
            </div>
            <!-- 描述 -->
            <div
                v-if="isOpen(props.posterDescSwitch)"
                ref="desc"
                :style="{
                    color: props.posterDescColor ?? 'transparent',
                    fontSize: props.posterDescSize + 'px',
                    top: formData.position.descY + 'px',
                    left: formData.position.descX + 'px'
                }"
                class="poster-desc"
                @mousedown.stop.capture="e => handleMoveElement(e, 'desc')"
                v-html="handleReplaceDesc(props.posterDescContent)"></div>

            <!-- 默认的二维码 -->
            <div
                ref="qrcode"
                class="poster-qrcode"
                :style="{
                    top: formData.position.qrcodeY + 'px',
                    left: formData.position.qrcodeX + 'px'
                }"
                @mousedown.stop.capture="e => handleMoveElement(e, 'qrcode')">
                <div class="qrcode">
                    <img src="@/assets/phonePreview/activityPhonePreview/posterPhone/qrcode.png" draggable="false" />
                </div>
            </div>
            <div id="poster-bg-canvas" ref="containerRef">
                <slot></slot>
                <img
                    v-if="props.url"
                    id="poster-canvas"
                    :src="props.url"
                    draggable="false"
                    @load="onLoadBackGroudImg" />
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, nextTick, watch, Ref, watchEffect } from 'vue';
import html2canvas from 'html2canvas';

import { uploadFile2 } from '@/api/common';
import { ElMessage } from 'element-plus';
import { cloneDeep } from 'lodash';

const loading = ref(false);

const props = defineProps({
    /** 海报背景 */
    url: {
        type: String,
        default: ''
    },
    /** 是否是圆形 ｜ 方形, 1:  圆形， 2: 方形*/
    posterHead: {
        type: Number,
        default: 1
    },
    /** 头像开关， 1是开，2是关 */
    posterHeadSwitch: {
        type: Number,
        default: 1
    },
    /** 昵称颜色 */
    posterNicknameColor: {
        type: String,
        default: '#fff'
    },
    /** 昵称大小 */
    posterNicknameSize: {
        type: [String, Number],
        default: 14
    },
    /** 昵称开关， 1是开，2是关 */
    posterNicknameSwitch: {
        type: Number,
        default: 1
    },
    /** 描述开关， 1是开，2是关 */
    posterDescSwitch: {
        type: Number,
        default: 1
    },
    /** 描述颜色 */
    posterDescColor: {
        type: String,
        default: '#fff'
    },
    /** 描述大小 */
    posterDescSize: {
        type: [String, Number],
        default: 12
    },
    /** 描述内容 */
    posterDescContent: {
        type: String,
        default: ''
    },
    /** 是否被禁用 */
    disabled: {
        type: Boolean,
        default: false
    }
});

const emit = defineEmits(['update:url']);

/** 是否展示。，1的情况下是展示 */
const isOpen = (number: number) => {
    return number === 1;
};
/* ----------------------------拖拽-start---------------------------- */
/**
 * @description 移动时更改当前元素的位置
 * @param {Element} val
 */
const handleMoveElement = (val: any, moveKey: string) => {
    // 禁用的效果下拖拽无效
    if (props.disabled) {
        return;
    }
    const moveCodeX = val.offsetX; // 鼠标当前x，初始x
    const moveCodeY = val.offsetY; // 鼠标当前y，初始y

    // 根据外容器的宽高当作最大和最小可移动位置
    const parentWrap: any = document.querySelector('.poster-canvas-wrap .poster-canvas-rect');

    const { left, top, width: MAX_WIDTH, height: MAX_HEIGHT } = parentWrap.getClientRects()[0];

    // 如果未上传图片，则拖拽无效
    if (MAX_HEIGHT === 0) {
        ElMessage.warning('请先选择海报图片');
        return;
    }
    // 获取当前移动的元素矩形集合
    const targetRect: any = val.target?.getClientRects()[0];

    // 获取宽高后续用来计算可移动的最大值
    const { width: SELF_WIDTH, height: SELF_HEIGHT } = targetRect;
    const handleMove = function (e: any) {
        // 鼠标当前移动的位置
        let configX = e.clientX - left - moveCodeX;
        let configY = e.clientY - top - moveCodeY;

        /**
         * @description 计算可移动的最大值, 最小值是0, 最大值是 外容器的宽高 - 自身的宽高
         * @param number 给元素的left或者top值
         * @param max 最大可移动发距离
         * @param maxAdd 一般来说是当前元素自身的宽高，需要计算最大值使用
         * @param min 最小移动距离。一般都是0
         */
        const getFinalNumber = (number: number, max: number, maxAdd: number, min: number) =>
            number < min ? min : number + maxAdd > max ? max - maxAdd : number;

        // 获取真实left位置
        configX = getFinalNumber(configX, MAX_WIDTH, SELF_WIDTH, 0);
        // 获取真实top位置
        configY = getFinalNumber(configY, MAX_HEIGHT, SELF_HEIGHT, 0);

        formData.value.position[`${moveKey}X`] = configX;
        formData.value.position[`${moveKey}Y`] = configY;
    };
    document.addEventListener('mousemove', handleMove);
    document.addEventListener('mouseup', () => {
        document.removeEventListener('mousemove', handleMove);
    });
};

const avatar = ref();
const nickname = ref();
const desc = ref();
const qrcode = ref();
/**
 * 重置元素的定位
 * 会根据当前的位置计算出真实位置防止切换图片后图片位置显示不正确的情况
 * @param ElementRefObj 元素的ref，也就是头像昵称描述二维码这几个
 */
const resetElementPostion = async (ElementRefObj?: { [prop: string]: Ref }) => {
    if (!props.url) {
        formData.value.position = cloneDeep(defaultPostion);
        return;
    }

    // 等待dom刷新之后在进行计算
    await nextTick();
    const elementRef = ElementRefObj || { avatar, nickname, desc, qrcode };

    // 根据外容器的宽高当作最大和最小可移动位置
    const parentWrap: any = document.querySelector('.poster-canvas-wrap .poster-canvas-rect');

    const { width: MAX_WIDTH, height: MAX_HEIGHT } = parentWrap.getBoundingClientRect();

    // 可能会遇到没有显示组件的时候。这种情况不处理
    if (!MAX_WIDTH && !MAX_HEIGHT) {
        return;
    }
    for (const [key, refDom] of Object.entries(elementRef)) {
        // 如果当前是关闭状态，则不做处理。
        if (!refDom.value) {
            continue;
        }

        // 获取当前元素的本身宽高,用来做最大和最小的判断
        const { width: SELF_WIDTH, height: SELF_HEIGHT } = refDom.value.getBoundingClientRect();

        /** 校验是否超出了最大位置或者最小位置 */
        const checkFinalNumber = (number: number, max: number, selfSize: number, min: number) => {
            // 当前位置小于最小值 或者 当前位置加上自身的大小 是否 超出最大值
            return number < min || number + selfSize > max;
        };
        // 校验是否超出位置
        const resultX = checkFinalNumber(formData.value.position[`${key}X`], MAX_WIDTH, SELF_WIDTH, 0);
        // 校验是否超出位置
        const resultY = checkFinalNumber(formData.value.position[`${key}Y`], MAX_HEIGHT, SELF_HEIGHT, 0);

        if (resultX || resultY) {
            formData.value.position[`${key}X`] = defaultPostion[`${key}X`];
            formData.value.position[`${key}Y`] = defaultPostion[`${key}Y`];
        }
    }
};

/**
 * 切换按钮时，如果是打开状态，那么在做一次重置的校验
 * 原因是resetElementPostion方法中ref如果获取不到（switch关闭状态下），获取的宽高是0。
 */
const createSwtichWatchFactory = (propsFn: () => number, ElementRefObj: { [prop: string]: Ref }) => {
    watch(propsFn, newVal => {
        if (isOpen(newVal)) {
            resetElementPostion(ElementRefObj);
        }
    });
};
// 监听头像开关
createSwtichWatchFactory(() => props.posterHeadSwitch, { avatar });
// 监听昵称开关
createSwtichWatchFactory(() => props.posterNicknameSwitch, { nickname });
// 监听描述开关
createSwtichWatchFactory(() => props.posterDescSwitch, { desc });

/* ----------------------------拖拽-end---------------------------- */

/* ----------------------------描述-start---------------------------- */

/**
 * @description 替换描述的左右尖括号，把/n替换成换行
 * @param val
 */
const handleReplaceDesc = (val: string) => {
    const result = val.replace(/\</g, '&lt;').replace(/\>/g, '&gt;').replace(/\n/g, '<br />');
    return result || '这里是显示描述';
};

/* ----------------------------描述-end---------------------------- */

/* ----------------------------海报地址-start---------------------------- */
const containerRef = ref();
/**
 * 把 #poster-bg-canvas 元素转化成图片
 * 然后同步至v-model:url
 */
const convertToImage = () => {
    // eslint-disable-next-line no-async-promise-executor
    return new Promise(async (resolve, reject) => {
        // 设置放大倍数

        // 传入节点原始宽高
        const width = containerRef.value.offsetWidth;
        const height = containerRef.value.offsetHeight;
        // html2canvas配置项
        const ops = {
            width,
            height,
            useCORS: true,
            allowTaint: false
        };
        const canvas = await html2canvas(containerRef.value, ops);
        // 返回图片的二进制数据
        canvas.toBlob(async (blob: any) => {
            const file = new File([blob], `${+new Date()}.png`);
            await uploadOss(file);
            resolve(file);
        });
    });
};

/** 上传oss然后给到外部 */
const uploadOss = async (uploadfFile: File) => {
    const res = await uploadFile2({
        file: uploadfFile,
        fileName: uploadfFile.name
    });

    emit('update:url', res.url);
};

/** 监听外部url变更实施变更canvas的背景 */
watch(
    () => props.url,
    async (val: string) => {
        if (!val) {
            posterBoxStyle.value = defaultImageStyle;

            // 如果没有url则使用默认值
            formData.value.position = cloneDeep(defaultPostion);
            return;
        }
    },
    {
        immediate: true
    }
);

/** 长图样式 */
const longImageStyle = {
    'align-self': 'flex-start'
};

/** 短图样式 */
const shortImageStyle = {
    'align-self': 'center'
};

/** 默认样式 */
const defaultImageStyle = {
    height: '603px',
    'align-self': 'center'
};
// 海报壳子样式
const posterBoxStyle = ref<any>(defaultImageStyle);

const onLoadBackGroudImg = () => {
    const bgImg: any = document.getElementById('poster-canvas');

    if (!bgImg) {
        return;
    }

    // 计算高度及其样式
    const scale = 340 / bgImg.naturalWidth;

    let height = bgImg.getBoundingClientRect().height;
    height = height ? height : bgImg.naturalHeight * scale;

    const phoneContent: any = document.querySelector('.poster-canvas-wrap') || {};
    if (height < (phoneContent.clientHeight || 0)) {
        posterBoxStyle.value = shortImageStyle;
    } else {
        posterBoxStyle.value = longImageStyle;
    }
    posterBoxStyle.value['height'] = height + 'px';
    formData.value.position.posterH = height;

    // 重置定位信息
    resetElementPostion();
};

/* ----------------------------海报地址-end---------------------------- */

const defaultPostion = {
    avatarX: 12,
    avatarY: 12,
    nicknameX: 70,
    nicknameY: 22,
    qrcodeX: 249,
    qrcodeY: 9,
    descX: 70,
    descY: 36,
    posterW: 340,
    posterH: 603
};
const formData = ref({
    position: cloneDeep(defaultPostion)
});

defineExpose({
    convertToImage,
    formData
});
</script>
<style lang="scss" scoped>
.poster-canvas-wrap {
    display: flex;
    justify-content: center;
    vertical-align: middle;
    line-height: 0;
    width: 342px;
    height: 640px;
    overflow-y: auto;
    overflow-x: hidden;

    .poster-canvas-rect {
        position: relative;
        width: 340px;
        height: 603px;
        outline: rgb(234 234 234) solid 1px;
    }

    .poster-avatar,
    .poster-title,
    .poster-desc,
    .poster-qrcode {
        z-index: 1;
        cursor: move;
        user-select: none;
        position: absolute;
        vertical-align: middle;
    }

    .poster-desc {
        line-height: calc(1em + 6px);
    }

    .poster-avatar {
        img {
            width: 50px;
            height: 50px;
        }
    }

    .poster-qrcode {
        img {
            width: 80px;
            height: 80px;
        }
    }

    .is-circle {
        img {
            border-radius: 50%;
        }
    }
}

#poster-canvas {
    width: 340px;
}

#poster-bg-canvas {
    position: relative;
}
</style>
